跳到主要内容

Spring 的 ApplicationContext 应用上下文学习

Spring ApplicationContext 是什么?

Spring 管理的这些类被称为 Bean,并且生活在 Spring 容器中。Bean 处理程序的最基本实现是 Bean Factory。一般 org.springframework.beans.factory.BeanFactory 接口的实现类,用于初始化,配置和管理 Bean 的容器。

但通常在 Spring 应用程序中仅使用 BeanFactory 是不够的。所以它出现在应用程序上下文中。

应用程序上下文(Application context)是一种面向企业化的 Bean 工厂。

作为标准 Bean 工厂,它是 Bean class 生活的空间。但与标准 Bean 工厂不同,应用程序上下文提供了一个补充企业层(也就是通用的东西了,比如企业里的胸牌,服装等)。

举个例子:例如,通过提供国际化,转换服务或事件传播,使我们省去很多麻烦去亲自处理。通常,应用程序上下文优于单独使用 Bean 工厂。

Application context 它的唯一缺点是内存消耗比 Bean 工厂大,出现这种情况是由于上面说的额外补充的服务。所以如果对内存要求非常苛刻,一般还是使用 Bean factory 的方式。

BeanFactory 和 ApplicationContext 的区别?

BeanFactory 和 ApplicationContext 是 Spring 的两大核心接口,都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。这里总结一下它们的区别

依赖关系

BeanFactory:是 Spring 里面最底层的接口,包含了各种 Bean 的定义,读取 bean 配置文档,管理 bean 的加载、实例化,控制 bean 的生命周期,维护 bean 之间的依赖关系。

ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具有的功能外,还提供了更完整的框架功能:比如说继承 MessageSource,因此支持国际化、统一的资源文件访问方式、提供在监听器中注册 bean 的事件、同时加载多个配置文件、载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层。

加载方式

BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用 getBean()),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。

如果 Bean 的某一个属性没有注入,BeanFacotry 加载后,直至第一次使用调用 getBean 方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext 启动后预载入所有的单实例 Bean,通过预载入单实例 bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

相对于基本的 BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢。

创建方式

BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader。

注册方式

BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。

ApplicationContext 通常的实现是什么?

1、FileSystemXmlApplicationContext :此容器从一个 XML 文件中加载 beans 的定义, XML Bean 配置文件的全路径名必须提供给它的构造函数。

2、ClassPathXmlApplicationContext:此容器也从一个 XML 文件中加载 beans 的定义,这里,你需要正确设置 classpath 因为这个容器将在 classpath 里找 bean 配置。

3、WebXmlApplicationContext:此容器加载一个 XML 文件,此文件定义了一个 WEB 应用的所有 bean。

Spring 的应用程序上下文类

关键部分就是 org.springframework.context.ApplicationContext 接口。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

如上可见它扩展了一些其他接口:

1、org.springframework.core.env.EnvironmentCapable 接口:用于标记对象来对外暴露自己说我实现了 Environment 接口。

public interface EnvironmentCapable {
Environment getEnvironment();
}

2、org.springframework.beans.factory.ListableBeanFactory 接口:通过继承该 interface 可以列出所有 bean,也可以只列出与预期类型相对应的 bean。

3、org.springframework.beans.factory.HierarchicalBeanFactory 接口:支持分层 bean 的管理。

4、org.springframework.context.MessageSource 接口:用来解决消息支持国际化。

5、org.springframework.context.ApplicationEventPublisher 接口:通过该接口,可以允许通知所有类来监听到某些应用程序上下文事件。

6、org.springframework.core.io.support.ResourcePatternResolver 接口:是一个有助于将资源地址(例如 classpath:/WEB-INF/web.xml)解析到 org.springframework.core.io.Resource 对象中的策略接口。

实例 XmlWebApplicationContext

实现了这些接口的应用程序上下文比一个简单的 Bean 工厂更有用。例如通过 org.springframework.web.context.support.XmlWebApplicationContext 这个实现类来看其在 Web 应用程序中使用。此类扩展了同一个包下 AbstractRefreshableWebApplicationContext这 个抽象类。

XmlWebApplicationContext 实现了 AbstractRefreshableApplicationContext 中的抽象方法 loadBeanDefinitions,用于读取所有 bean。从这个方法实现,可以看出,所有的 bean 都是通过 org.springframework.beans.factory.xml.XmlBeanDefinitionReader 从 XML 文件读取的。

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

补充:Environment 接口

用来表示整个应用运行时的环境,为了更形象地理解 Environment,可以把 Spring 应用的运行时简单地想象成两个部分:一个是 Spring 应用本身,一个是 Spring 应用所处的环境。

Environment 在容器中是一个抽象的集合,是指应用环境的2个方面:profiles 和properties。

Properties

properties 属性可能来源于 properties 文件、JVM properties、system 环境变量、JNDI、servlet context parameters 上下文参数、专门的 properties 对象,Maps等等。

Environment对象的作用,对于 properties 来说,是提供给用户方便的服务接口、方便撰写配置、方便解析配置。

例如获取属性,可以有两种方式

  • 实现 EnvironmentAware 接口。
  • @Inject 或者 @Autowired 一个 Environment 对象。

绝大数情况下,bean 都不需要直接访问 Environment 对象,而是通过类似 @Value 注解的方式把属性值注入进来。

Profile

很多时候,我们项目在开发环境和生成环境的环境配置是不一样的,例如,数据库配置,在开发的时候,我们一般用测试数据库,而在生产环境的时候,我们是用正式的数据,这时候,我们可以利用 profile 在不同的环境下配置用不同的配置文件或者不同的配置。

Environment 环境对象的作用,对于 profiles 配置来说,它能决定当前激活的是哪个 profile 配置,和哪个 profile 是默认。(说白了就是决定当前使用的是哪套环境)

Spring Boot 允许你通过命名约定按照一定的格式 application-{profile}.properties 来定义多个配置文件,然后通过在 application.properyies 通过 spring.profiles.active 来具体激活一个或者多个配置文件,如果没有没有指定任何 profile 的配置文件的话,Spring Boot 默认会启动 application-default.properties

具体使用

可以通过 @Autowired 织入 Environment

@Autowired
private Environment environment;

也可以通过实现 implements EnvironmentAware 然后实现接口中的方法

// 这里用 Lambda 代替
@Setter
private Environment environment;

常用功能,获取属性配制文件中的值

// 获取配置文件的属性值
environment.getProperty("rabbitmq.address")

// 获取是否使用 profile 的
public boolean isDev(){
boolean devFlag = environment.acceptsProfiles("dev");
return devFlag;
}

Reference

参考资料 Profile配置和加载配置文件 参考资料 Spring5源码解析-Spring中的应用上下文